// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Internal.Text;
using Internal.TypeSystem;
using System;
using System.Diagnostics;

namespace ILCompiler
{
    //
    // The naming format of these names is known to the debugger
    //
    public class WindowsNodeMangler : NodeMangler
    {
        private TargetDetails _target;

        public static Utf8String NonGCStaticMemberName = "__NONGCSTATICS";
        public static Utf8String GCStaticMemberName = "__GCSTATICS";
        public static Utf8String ThreadStaticMemberName = "__THREADSTATICS";
        public static Utf8String ThreadStaticIndexName = "__THREADSTATICINDEX";

        public WindowsNodeMangler(TargetDetails target)
        {
            _target = target;
        }

        // Mangled name of boxed version of a type
        public sealed override Utf8String MangledBoxedTypeName(TypeDesc type)
        {
            Debug.Assert(type.IsValueType);
            return Utf8String.Concat("Boxed_"u8, NameMangler.GetMangledTypeName(type).AsSpan());
        }

        public sealed override Utf8String MethodTable(TypeDesc type)
        {
            Utf8String mangledJustTypeName = type.IsValueType
                ? MangledBoxedTypeName(type)
                : NameMangler.GetMangledTypeName(type);

            // "??_7TypeName@@6B@" is the C++ mangling for "const TypeName::`vftable'"
            // This, along with LF_VTSHAPE debug records added by the object writer
            // is the debugger magic that allows debuggers to vcast types to their bases.
            return Utf8String.Concat("??_7"u8, mangledJustTypeName.AsSpan(), "@@6B@"u8);
        }

        private Utf8String CreateStaticFieldName(TypeDesc type, ReadOnlySpan<byte> fieldName)
        {
            return Utf8String.Concat("?"u8, fieldName, "@"u8, NameMangler.GetMangledTypeName(type).AsSpan(), "@@"u8);
        }

        public sealed override Utf8String GCStatics(TypeDesc type)
        {
            return CreateStaticFieldName(type, GCStaticMemberName.AsSpan());
        }

        public sealed override Utf8String NonGCStatics(TypeDesc type)
        {
            return CreateStaticFieldName(type, NonGCStaticMemberName.AsSpan());
        }

        public sealed override Utf8String ThreadStatics(TypeDesc type)
        {
            Utf8String name = NameMangler.CompilationUnitPrefix.Length > 0
                ? Utf8String.Concat(NameMangler.CompilationUnitPrefix, ThreadStaticMemberName)
                : ThreadStaticMemberName;

            return CreateStaticFieldName(type, name.AsSpan());
        }

        public sealed override Utf8String ThreadStaticsIndex(TypeDesc type)
        {
            return CreateStaticFieldName(type, ThreadStaticIndexName.AsSpan());
        }

        public sealed override Utf8String TypeGenericDictionary(TypeDesc type)
        {
            return Utf8String.Concat(GenericDictionaryNamePrefix, NameMangler.GetMangledTypeName(type));
        }

        public sealed override Utf8String MethodGenericDictionary(MethodDesc method)
        {
            return Utf8String.Concat(GenericDictionaryNamePrefix, NameMangler.GetMangledMethodName(method));
        }

        public sealed override Utf8String ExternMethod(Utf8String unmangledName, MethodDesc method)
        {
            if (_target.Architecture != TargetArchitecture.X86)
            {
                return unmangledName;
            }

            UnmanagedCallingConventions callConv;
            if (method.IsPInvoke)
            {
                callConv = method.GetPInvokeMethodCallingConventions() & UnmanagedCallingConventions.CallingConventionMask;
            }
            else if (method.IsUnmanagedCallersOnly)
            {
                if (method is not Internal.TypeSystem.Ecma.EcmaMethod)
                    callConv = method.Signature.GetStandaloneMethodSignatureCallingConventions();
                else
                    callConv = method.GetUnmanagedCallersOnlyMethodCallingConventions() & UnmanagedCallingConventions.CallingConventionMask;
            }
            else
            {
                Debug.Assert(method is Internal.TypeSystem.Ecma.EcmaMethod ecmaMethod && (ecmaMethod.GetRuntimeImportName() != null || ecmaMethod.GetRuntimeExportName() != null));
                return unmangledName;
            }

            int signatureBytes = 0;
            foreach (var p in method.Signature)
            {
                signatureBytes += AlignmentHelper.AlignUp(p.GetElementSize().AsInt, _target.PointerSize);
            }

            return callConv switch
            {
                UnmanagedCallingConventions.Stdcall => $"_{unmangledName}@{signatureBytes}",
                UnmanagedCallingConventions.Fastcall => $"@{unmangledName}@{signatureBytes}",
                UnmanagedCallingConventions.Cdecl => $"_{unmangledName}",
                _ => throw new System.NotImplementedException()
            };
        }

        public sealed override Utf8String ExternVariable(Utf8String unmangledName)
        {
            if (_target.Architecture != TargetArchitecture.X86)
            {
                return unmangledName;
            }

            return $"_{unmangledName}";
        }
    }
}
